﻿#region Imports

using System;
using System.Linq.Expressions;
using System.Text;
using Remotion.Linq.Clauses.ExpressionTreeVisitors;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Parsing;
using System.Diagnostics;
using System.Collections.Generic;

#endregion

namespace CITPRO.Linq.LinqToOdbc.SqlQueryGeneration {
    internal class ExpressionTreeVisitor : ThrowingExpressionTreeVisitor {
        private readonly QueryPartsAggregator queryPartsAggregator;
        private readonly IDialectProvider dialectProvider;

        private readonly static Dictionary<ExpressionType, string> expressionTypeLookup = new Dictionary<ExpressionType, string>()
            {
                {ExpressionType.Equal, "="},
                {ExpressionType.AndAlso,"AND"},
                {ExpressionType.And,"AND"},
                {ExpressionType.Or,"OR"},
                {ExpressionType.OrElse,"OR"},
                {ExpressionType.Add,"+"},
                {ExpressionType.Subtract,"-"},
                {ExpressionType.Multiply,"*"},
                {ExpressionType.Divide,"/"},
                {ExpressionType.GreaterThan,">"},
                {ExpressionType.GreaterThanOrEqual,">="},
                {ExpressionType.LessThan,"<"},
                {ExpressionType.LessThanOrEqual,"<="},
                {ExpressionType.NotEqual,"<>"},
                {ExpressionType.Not,"NOT"}
            };

        protected override Expression VisitUnaryExpression(UnaryExpression expression) {

            Debug.WriteLine("VisitUnaryExpression");
            Debug.Indent();

            if (expressionTypeLookup.ContainsKey(expression.NodeType)) {
                Debug.WriteLine(expressionTypeLookup[expression.NodeType]);
                queryPartsAggregator.AddOperator(expressionTypeLookup[expression.NodeType]);
                VisitAndConvert(expression.Operand, "VisitUnaryExpression");

            } else {
                throw new NotSupportedException("Expression not supported:" + expression.NodeType.GetType().Name);
            }
            Debug.Unindent();
            return expression;
        }

        private ExpressionTreeVisitor(QueryPartsAggregator queryPartsAggregator, IDialectProvider dialectProvider) {
            this.queryPartsAggregator = queryPartsAggregator;
            this.dialectProvider = dialectProvider;
        }


        public static void EvaluateExpression(Expression linqExpression, QueryPartsAggregator queryPartsAggregator, IDialectProvider dialectProvider) {
            var visitor = new ExpressionTreeVisitor(queryPartsAggregator, dialectProvider);
            visitor.VisitExpression(linqExpression);
        }

        protected override Expression VisitSubQueryExpression(SubQueryExpression expression) {
            queryPartsAggregator.AddLeftSideContainer();
            //var sql = QueryModelVisitor.GenerateSqlQuery(expression.QueryModel);
            queryPartsAggregator.AddToWhere(QueryModelVisitor.GenerateSqlQuery(expression.QueryModel,dialectProvider, queryPartsAggregator));
            queryPartsAggregator.AddRightSideContainer();
            return expression;
        }

        protected override Expression VisitQuerySourceReferenceExpression(QuerySourceReferenceExpression expression) {
            Debug.WriteLine("VisitQuerySourceReferenceExpression");
            Debug.Indent();
            Debug.WriteLine("Entity", expression.ReferencedQuerySource.ItemType.Name);
            queryPartsAggregator.AddMemberExpression(expression.ReferencedQuerySource.ItemType);
            //queryPartsAggregator.AddFromTable(expression.ReferencedQuerySource.ItemType, expression.ReferencedQuerySource.ItemName);
            Debug.Unindent();
            return expression;
        }

        protected override Expression VisitNewExpression(NewExpression expression) {
            Debug.WriteLine("VisitNewExpression");
            Debug.Indent();
            foreach (var item in expression.Arguments) {
                VisitAndConvert(item, "VisitNewExpression");
            }
            Debug.Unindent();
            return expression;
        }
        protected override Expression VisitMemberExpression(MemberExpression expression) {
            Debug.WriteLine("VisitMemberExpression");
            Debug.WriteLine(expression.Member.ReflectedType.Name, "Entity");
            Debug.WriteLine(expression.Member.Name, "Field");
            queryPartsAggregator.AddMemberExpression(expression.Member.ReflectedType, expression.Member.Name);
            return expression;
        }

        protected override Expression VisitBinaryExpression(BinaryExpression expression) {
            Debug.WriteLine("VisitBinaryExpression");
            Debug.Indent();
            queryPartsAggregator.AddLeftSideContainer();

            Debug.WriteLine("Left");
            Debug.Indent();
            VisitExpression(expression.Left);

            if (expressionTypeLookup.ContainsKey(expression.NodeType)) {
                Debug.WriteLine(expressionTypeLookup[expression.NodeType]);
                queryPartsAggregator.AddOperator(expressionTypeLookup[expression.NodeType]);
            } else {
                Debug.WriteLine("Expression not in lookup: {0}", expression.NodeType.ToString());
                VisitBinaryExpression(expression);
            }

            Debug.Unindent();
            Debug.WriteLine("Right");
            Debug.Indent();
            VisitExpression(expression.Right);
            queryPartsAggregator.AddRightSideContainer();
            Debug.Unindent();

            Debug.Unindent();
            return expression;
        }


        protected override Expression VisitConstantExpression(ConstantExpression expression) {
            Debug.WriteLine("VisitConstantExpression");
            Debug.Indent();
            Debug.WriteLine(expression.Value, "Value");
            Debug.WriteLine(expression.Type.Name, "Type");
            if (expression.Value is DateTime || expression.Value is DateTime?) {
                queryPartsAggregator.AddConstantExpression(expression.Type, dialectProvider.FormatForDate(expression.Value));
            } else {
                queryPartsAggregator.AddConstantExpression(expression.Type, expression.Value);
            }
            Debug.Unindent();
            return expression;
        }

        protected override Exception CreateUnhandledItemException<T>(T unhandledItem, string visitMethod) {
            string itemText = FormatUnhandledItem(unhandledItem);
            var message = string.Format("The expression '{0}' (type: {1}) is not supported by this LINQ provider.",
                                        itemText, typeof(T));
            return new NotSupportedException(message);
        }

        private string FormatUnhandledItem<T>(T unhandledItem) {
            var itemAsExpression = unhandledItem as Expression;
            return itemAsExpression != null
                       ? FormattingExpressionTreeVisitor.Format(itemAsExpression)
                       : unhandledItem.ToString();
        }
    }
}